﻿namespace EFS.RC.CC.Sessions.OfficeService.Operators
{
	/// <summary>
	/// Customer session
	/// </summary>
	class DialIn : EFS.RC.CC.Sessions.OfficeService.Operators.Operator
	{
		#region Composition

		// Office conversation
		private Microsoft.Rtc.Collaboration.Conversation officeConversation;

		// Customer conversation
		private Microsoft.Rtc.Collaboration.Conversation operatorConversation = null;

		// Customer call
		private Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall inboundOfficeAudioVideoCall = null;

		// office call
		private Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall outboundOperatorAudioVideoCall = null;

		// back to back call into office conversation
		private Microsoft.Rtc.Collaboration.BackToBackCall operatorConversationB2BCall;

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="customersCustomerSession.CustomersOfficeServiceEndPointApplication"></param>
		/// <param name="toHeader"></param>
		internal DialIn(EFS.RC.CC.Presences.Operators.Presence operatorPresence, EFS.RC.CC.Sessions.OfficeService.Customers.Customer customerSession)
			: base(operatorPresence, customerSession)
		{
			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "DialIn:ctor", operatorPresence.User.SIP, GetType());
		}

		#endregion

		#region Usage

		/// <summary>
		/// Describe this object into a nice formated string
		/// </summary>
		/// <returns></returns>
		public override System.String Describe()
		{
			return System.String.Format("EFS.RC.CC.Sessions.PublicOfficeService.Operators.DialIn [{0}] {1}", CustomerSession.OfficeServiceConference.Describe(), OperatorPresence.User.SIP);
		}

		/// <summary>
		/// Handle incomming call from conference
		/// </summary>
		/// <param name="call"></param>
		internal void HandleReplacedCall(Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall transferredAudioVideoCall)
		{
			// check
			EFS.Common.Verify.Reference(transferredAudioVideoCall, "transferredAudioVideoCall");

			// check not already handled
			// ~ commented for performance ~ EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Dial In Session is not Started");
				}

				// get app
				EFS.RC.CC.Applications.EndPoints.OfficeService.Operators operatorsOfficeServiceEndPointApplication = CustomerSession.
					CustomersOfficeServiceEndPointApplication.
					ApplicationPresence.
					EndPointsPresencesRegistrar.
					OperatorOfficeServiceEndPointPresence.
					PresenceEndPointApplication;

				// get agent for an external invite
				EFS.RC.CC.Presences.Operators.Presence presence = operatorsOfficeServiceEndPointApplication.OperatorsTeam.GetAgentBySIP(transferredAudioVideoCall.RemoteEndpoint.Participant.Uri);

				// check
				if (null == presence)
				{
					// make anon operator
					presence = new EFS.RC.CC.Presences.Operators.Anonymous(transferredAudioVideoCall.RemoteEndpoint.Participant.Uri);
				}

				// make a session
				EFS.RC.CC.Sessions.OfficeService.Operators.Transfer operatorTransferSession = new EFS.RC.CC.Sessions.OfficeService.Operators.Transfer(presence, CustomerSession, transferredAudioVideoCall);
				operatorTransferSession.AsinkEstablishedDelegate.Register(OnOperatorTransferSessionEstablished);

				// start agent session
				operatorTransferSession.BeginStartup();
			}
		}

		/// <summary>
		/// Handle incomming call from conference
		/// </summary>
		/// <param name="call"></param>
		internal EFS.Common.Async.AsyncProcess HandleIncommingCall(Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall audioVideoCall)
		{
			EFS.EventLog.Log.LogInfo("HandleIncommingCall", this);

			// check
			EFS.Common.Verify.Reference(audioVideoCall, "audioVideoCall");

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check not already handled
				if (null != inboundOfficeAudioVideoCall)
				{
					throw new System.InvalidOperationException("Conference call already handled");
				}

				// keep;
				inboundOfficeAudioVideoCall = audioVideoCall;
			}

			// make B2B 
			return BeginAsyncProcess2(MakeB2BCall);
		}

		#endregion

		#region Startup

		/// <summary>
		/// Make back2back call for customer into conference
		/// </summary>
		protected override void SetupSession()
		{
			EFS.EventLog.Log.LogInfo("SetupSession", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "SetupSession", this);

			// register opertor session
			CustomerSession.OfficeServiceConference.SetOperatorSession(this);

			// call office onference in preparation for b2b call
			BeginAsyncProcess2(JoinOfficeConference);
		}

		/// <summary>
		/// Make back2back call for customer into conference
		/// </summary>
		private void JoinOfficeConference()
		{
			EFS.EventLog.Log.LogInfo("JoinOfficeConference", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "JoinOfficeConference", this);

			// get conference uri
			System.String conferenceUri = CustomerSession.OfficeServiceConference.ConferenceUri;

			// get end point
			Microsoft.Rtc.Collaboration.ApplicationEndpoint applicationEndpoint = CustomerSession.CustomersOfficeServiceEndPointApplication.CollaborationApplicationEndpoint;

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// make office conversation
				officeConversation = new Microsoft.Rtc.Collaboration.Conversation(applicationEndpoint);
				officeConversation.Impersonate(ParticipantEndPointUri, null, CustomerSession.DisplayName);
				officeConversation.ApplicationContext = this;
				officeConversation.StateChanged += OnOfficeConversationStateChanged;

				// conference options
				Microsoft.Rtc.Collaboration.ConferenceJoinOptions conferenceJoinOptions = new Microsoft.Rtc.Collaboration.ConferenceJoinOptions();
				conferenceJoinOptions.JoinMode = Microsoft.Rtc.Collaboration.JoinMode.Default;

				// join
				officeConversation.ConferenceSession.BeginJoin(
					conferenceUri,
					conferenceJoinOptions,
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(EndJoinOfficeConference, null));
			}
		}

		/// <summary>
		/// End join
		/// </summary>
		/// <param name="result"></param>
		private void EndJoinOfficeConference(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("EndJoinOfficeConference", this);

			try
			{
				// end join
				officeConversation.ConferenceSession.EndJoin(ar);

				// call office conference
				BeginAsyncProcess2(CallOfficeConference);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Dial into office conference
		/// </summary>
		private void CallOfficeConference()
		{
			EFS.EventLog.Log.LogInfo("CallOfficeConference", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// dial out option
				Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoMcuDialOutOptions options = new Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoMcuDialOutOptions();
				options.PrivateAssistantDisabled = true;
				options.Media.Add(new Microsoft.Rtc.Collaboration.McuMediaChannel(Microsoft.Rtc.Collaboration.MediaType.Audio, Microsoft.Rtc.Collaboration.McuMediaChannelStatus.SendReceive));
				options.ParticipantUri = ParticipantEndPointUri;

				// dial out
				officeConversation.ConferenceSession.AudioVideoMcuSession.BeginDialOut(
					CustomerSession.CustomersOfficeServiceEndPointApplication.CollaborationApplicationEndpoint.EndpointUri,
					options,
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(EndDialOut));
			}
		}

		/// <summary>
		/// End establish
		/// </summary>
		/// <param name="result"></param>
		private void EndDialOut(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("EndDialOut", this);

			try
			{
				// check
				officeConversation.ConferenceSession.AudioVideoMcuSession.EndDialOut(ar);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex, OnException);
			}
		}

		/// <summary>
		/// Make back2back call for customer into conference
		/// </summary>
		private void MakeB2BCall()
		{
			EFS.EventLog.Log.LogInfo("MakeB2BCall", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// EFS.EventLog.Log.LogExtended("MakeB2BCall: Entering lock", this);
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Only Started Custmer Session can MakeB2BCall");
				}

				// log impersonation
				EFS.EventLog.Log.LogInfo(System.String.Format("Impersonate operatorConversation {0}", CustomerSession.CustomerUri), this);

				// Make operator conversation
				operatorConversation = new Microsoft.Rtc.Collaboration.Conversation(inboundOfficeAudioVideoCall.Conversation.Endpoint);
				operatorConversation.Impersonate(CustomerSession.CustomerUri, null, CustomerSession.DisplayName);
				operatorConversation.ApplicationContext = this;

				// make call
				outboundOperatorAudioVideoCall = new Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall(operatorConversation);

				// handle transfer and FW
				outboundOperatorAudioVideoCall.TransferReceived += OnAudioVideoCallTransferReceived;
				outboundOperatorAudioVideoCall.Forwarded += OnAudioVideoCallForwarded;
				
				// inbound call settings
				Microsoft.Rtc.Collaboration.BackToBackCallSettings inboundB2BCallSettings = new Microsoft.Rtc.Collaboration.BackToBackCallSettings(inboundOfficeAudioVideoCall);

				// log dialing out
				EFS.EventLog.Log.LogInfo(System.String.Format("Dialing Out to Phone: {0}", OperatorPresence.User.SIP), this);

				// outbound call settings
				Microsoft.Rtc.Collaboration.BackToBackCallSettings outoundB2BCallSettings = new Microsoft.Rtc.Collaboration.BackToBackCallSettings(outboundOperatorAudioVideoCall, OperatorPresence.User.SIP);
				Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCallEstablishOptions outoundB2BCallOptions = new Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCallEstablishOptions();
				outoundB2BCallSettings.CallEstablishOptions = outoundB2BCallOptions;

				// make call
				operatorConversationB2BCall = new Microsoft.Rtc.Collaboration.BackToBackCall(
					outoundB2BCallSettings,
					inboundB2BCallSettings);

				// establish call
				operatorConversationB2BCall.BeginEstablish(
						EFS.Common.Async.AsyncResult.StateDelegate.End,
						EFS.Common.Async.AsyncResult.StateDelegate.State(OnEndEstablishB2BCall));
			}
		}

		/// <summary>
		/// End B2B call
		/// </summary>
		/// <param name="ar"></param>
		private void OnEndEstablishB2BCall(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("OnEndEstablishB2BCall", this);

			try
			{
				// end establish call
				operatorConversationB2BCall.EndEstablish(ar);

				// established
				AsinkEstablishedDelegate.Start();
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex, OnException);
			}
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Agent session established
		/// </summary>
		private void OnOperatorTransferSessionEstablished(EFS.RC.CC.Sessions.OfficeService.IParticipant officeServiceSessionParticipant)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnSessionTerminated", this);

			// check
			EFS.Common.Verify.Reference(officeServiceSessionParticipant, "officeServiceSessionParticipant");

			// cast
			EFS.RC.CC.Sessions.OfficeService.Operators.Operator operatorSession = officeServiceSessionParticipant as EFS.RC.CC.Sessions.OfficeService.Operators.Operator;

			// check
			EFS.Common.Verify.Reference(operatorSession, "operatorSession");

			// sync
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// Transfer DDI
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void OnAudioVideoCallTransferReceived(object sender, Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCallTransferReceivedEventArgs e)
		{
			try
			{
				// make tranfer 
				EFS.RC.CC.Tools.Functions.Operator.Trasfer transfer = new EFS.RC.CC.Tools.Functions.Operator.Trasfer(this, e.TransferDestination);

				// start transfer
				transfer.BeginTransfer();
			}
			catch (System.Exception ex)
			{
				// accept
				e.Decline();

				// log
				EFS.EventLog.Log.LogException(ex, this);
			}
		}

		/// <summary>
		/// FW DDI
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void OnAudioVideoCallForwarded(object sender, Microsoft.Rtc.Collaboration.CallForwardReceivedEventArgs e)
		{
			try
			{
				// make tranfer 
				EFS.RC.CC.Tools.Functions.Operator.Forward forward = new EFS.RC.CC.Tools.Functions.Operator.Forward(this, e.ForwardDestination);

				// start transfer
				forward.BeginForward();
			}
			catch (System.Exception ex)
			{
				// accept
				e.Decline();

				// log
				EFS.EventLog.Log.LogException(ex, this);
			}
		}

		/// <summary>
		/// State changed for office conersation
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		private void OnOfficeConversationStateChanged(object sender, Microsoft.Rtc.Signaling.StateChangedEventArgs<Microsoft.Rtc.Collaboration.ConversationState> args)
		{
			EFS.EventLog.Log.LogInfo("OnOfficeConversationStateChanged", this);

			try
			{
				// check terminating
				if (args.State == Microsoft.Rtc.Collaboration.ConversationState.Terminating)
				{
					// log
					EFS.EventLog.Log.LogInfo("Shutting down - office conversation is beyign terminated", this);

					// start 
					BeginShutdown();
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// State changed for office conersation
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		private void OnCustomerConversationStateChanged(object sender, Microsoft.Rtc.Signaling.StateChangedEventArgs<Microsoft.Rtc.Collaboration.ConversationState> args)
		{
			EFS.EventLog.Log.LogInfo("OnCustomerConversationStateChanged", this);

			try
			{
				// check terminating
				if (args.State == Microsoft.Rtc.Collaboration.ConversationState.Terminating)
				{
					// log
					EFS.EventLog.Log.LogInfo("Shutting down - office conversation is beyign terminated", this);

					// start 
					BeginShutdown();
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}
		}

		#endregion

		#region Shutdown

		/// <summary>
		/// <summary>
		/// Remove customer from office conference
		/// </summary>
		protected override void CleanupSession()
		{
			// log
			EFS.EventLog.Log.LogInfo("CleanupSession", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "CleanupSession", this);

			// make termination
			EFS.RC.CC.Tools.Functions.Termination termination = new EFS.RC.CC.Tools.Functions.Termination(this);

			// remove delegate
			operatorConversation.StateChanged -= OnCustomerConversationStateChanged;

			// terminate all
			termination.Terminate(operatorConversationB2BCall, OnEndTerminateB2BCall);
			termination.Terminate(officeConversation, EndTerminateOfficeConversation);
		}

		/// End B2B
		/// </summary>
		/// <param name="ar"></param>
		private void OnEndTerminateB2BCall(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("OnEndTerminateB2BCall", this);

			try
			{
				// end terminate
				operatorConversationB2BCall.EndTerminate(ar);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// End conversation
		/// </summary>
		/// <param name="ar"></param>
		private void EndTerminateOfficeConversation(System.IAsyncResult ar)
		{
			// log
			EFS.EventLog.Log.LogInfo("EndTerminateConversation", this);

			try
			{
				// remove delegate
				officeConversation.StateChanged -= OnOfficeConversationStateChanged;

				// end terminate
				officeConversation.EndTerminate(ar);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		#endregion
	}
}
